Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.79% covered (success)
95.79%
91 / 95
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Serializer
95.79% covered (success)
95.79%
91 / 95
66.67% covered (warning)
66.67%
4 / 6
27
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 normalize
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
12.05
 denormalizeOnMethodCall
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 denormalizeNewObject
93.33% covered (success)
93.33%
28 / 30
0.00% covered (danger)
0.00%
0 / 1
10.03
 denormalizeOnExistingObject
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Serializer;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Exceptions\InvalidTypeException;
6use Apie\Core\Lists\ItemHashmap;
7use Apie\Core\Lists\ItemList;
8use Apie\Core\Metadata\Concerns\UseContextKey;
9use Apie\Core\Metadata\MetadataFactory;
10use Apie\Core\ValueObjects\Utils;
11use Apie\Serializer\Context\ApieSerializerContext;
12use Apie\Serializer\Context\NormalizeChildGroup;
13use Apie\Serializer\Exceptions\ValidationException;
14use Apie\Serializer\Lists\NormalizerList;
15use Apie\Serializer\Normalizers\BooleanNormalizer;
16use Apie\Serializer\Normalizers\DateTimeNormalizer;
17use Apie\Serializer\Normalizers\DateTimeZoneNormalizer;
18use Apie\Serializer\Normalizers\DoNotChangeFileNormalizer;
19use Apie\Serializer\Normalizers\EnumNormalizer;
20use Apie\Serializer\Normalizers\FloatNormalizer;
21use Apie\Serializer\Normalizers\IdentifierNormalizer;
22use Apie\Serializer\Normalizers\IntegerNormalizer;
23use Apie\Serializer\Normalizers\ItemListNormalizer;
24use Apie\Serializer\Normalizers\PaginatedResultNormalizer;
25use Apie\Serializer\Normalizers\PolymorphicObjectNormalizer;
26use Apie\Serializer\Normalizers\ReflectionTypeNormalizer;
27use Apie\Serializer\Normalizers\ResourceNormalizer;
28use Apie\Serializer\Normalizers\StringableCompositeValueObjectNormalizer;
29use Apie\Serializer\Normalizers\StringNormalizer;
30use Apie\Serializer\Normalizers\UploadedFileNormalizer;
31use Apie\Serializer\Normalizers\ValueObjectNormalizer;
32use Exception;
33use Psr\Http\Message\UploadedFileInterface;
34use ReflectionClass;
35use ReflectionMethod;
36
37class Serializer
38{
39    use UseContextKey;
40
41    public function __construct(private NormalizerList $normalizers)
42    {
43    }
44
45    public static function create(): self
46    {
47        return new self(new NormalizerList([
48            new PaginatedResultNormalizer(),
49            new DoNotChangeFileNormalizer(),
50            new UploadedFileNormalizer(),
51            new IdentifierNormalizer(),
52            new StringableCompositeValueObjectNormalizer(),
53            new PolymorphicObjectNormalizer(),
54            new DateTimeNormalizer(),
55            new DateTimeZoneNormalizer(),
56            new ResourceNormalizer(),
57            new EnumNormalizer(),
58            new ValueObjectNormalizer(),
59            new StringNormalizer(),
60            new IntegerNormalizer(),
61            new FloatNormalizer(),
62            new BooleanNormalizer(),
63            new ItemListNormalizer(),
64            new ReflectionTypeNormalizer(),
65        ]));
66    }
67
68    public function normalize(mixed $object, ApieContext $apieContext, bool $forceDefaultNormalization = false): string|int|float|bool|ItemList|ItemHashmap|null
69    {
70        $serializerContext = new ApieSerializerContext($this, $apieContext);
71        if (!$forceDefaultNormalization) {
72            foreach ($this->normalizers->iterateOverNormalizers() as $normalizer) {
73                if ($normalizer->supportsNormalization($object, $serializerContext)) {
74                    return $normalizer->normalize($object, $serializerContext);
75                }
76            }
77        }
78        if (is_array($object)) {
79            $count = 0;
80            $returnValue = [];
81            $isList = true;
82            foreach ($object as $key => $value) {
83                if ($key === $count) {
84                    $count++;
85                } else {
86                    $isList = false;
87                }
88                $returnValue[$key] = $serializerContext->normalizeChildElement($key, $value);
89            }
90            return $isList ? new ItemList($returnValue) : new ItemHashmap($returnValue);
91        }
92        if (!is_object($object)) {
93            if (in_array(get_debug_type($object), ['resource', 'resource (closed)'])) {
94                throw new InvalidTypeException($object, 'primitive');
95            }
96            return $object;
97        }
98        $metadata = MetadataFactory::getResultMetadata(new ReflectionClass($object), $apieContext);
99        $returnValue = [];
100
101        foreach ($metadata->getHashmap()->filterOnContext($apieContext, getters: true) as $fieldName => $metadata) {
102            if ($metadata->isField()) {
103                $returnValue[$fieldName] = $serializerContext->normalizeChildElement(
104                    $fieldName,
105                    $metadata->getValue($object, $apieContext)
106                );
107            }
108        }
109        return new ItemHashmap($returnValue);
110    }
111
112    public function denormalizeOnMethodCall(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $input, ?object $object, ReflectionMethod $method, ApieContext $apieContext): mixed
113    {
114        $serializerContext = new ApieSerializerContext($this, $apieContext);
115        try {
116            $arguments = $serializerContext->denormalizeFromMethod($input, $method);
117        } catch (Exception $error) {
118            throw ValidationException::createFromArray(['' => $error]);
119        }
120        return $method->invokeArgs($object, $arguments);
121    }
122
123    public function denormalizeNewObject(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $object, string $desiredType, ApieContext $apieContext): mixed
124    {
125        if (is_array($object)) {
126            $isList = false;
127            if ($desiredType === 'mixed') {
128                $isList = true;
129                $count = 0;
130                foreach (array_keys($object) as $key) {
131                    if ($key === $count) {
132                        $count++;
133                    } else {
134                        $isList = false;
135                        break;
136                    }
137                }
138            }
139            $object = $isList ? new ItemList($object) : new ItemHashmap($object);
140        }
141        if ($desiredType === 'mixed') {
142            return $object;
143        }
144        $serializerContext = new ApieSerializerContext($this, $apieContext);
145        foreach ($this->normalizers->iterateOverDenormalizers() as $denormalizer) {
146            if ($denormalizer->supportsDenormalization($object, $desiredType, $serializerContext)) {
147                return $denormalizer->denormalize($object, $desiredType, $serializerContext);
148            }
149        }
150        $refl = new ReflectionClass($desiredType);
151        if (!$refl->isInstantiable()) {
152            throw new InvalidTypeException($desiredType, 'a instantiable object');
153        }
154        $metadata = MetadataFactory::getCreationMetadata(
155            $refl,
156            $apieContext
157        );
158        $group = new NormalizeChildGroup(
159            $serializerContext,
160            $metadata
161        );
162        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
163        return $normalizedData->createNewObject();
164    }
165
166    public function denormalizeOnExistingObject(ItemHashmap $object, object $existingObject, ApieContext $apieContext): mixed
167    {
168        $refl = new ReflectionClass($existingObject);
169        $serializerContext = new ApieSerializerContext($this, $apieContext);
170        $metadata = MetadataFactory::getModificationMetadata(
171            $refl,
172            $apieContext
173        );
174        $group = new NormalizeChildGroup(
175            $serializerContext,
176            $metadata
177        );
178        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
179        return $normalizedData->modifyExistingObject($existingObject);
180    }
181}